package flowise;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import flowise.entity.IncomingInput;
import flowise.entity.NodeData;
import flowise.entity.ReactFlowEdge;
import flowise.entity.ReactFlowNode;
import flowise.entity.table.Chatflow;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author huangya
 * @date 2024/1/26
 */
public class FlowParse {

    public static void main(String[] args) {
        String chatflowId = "test";
        String incomingInputStr = "{\"question\":\"你好\",\"chatId\":\"19e7f2cd-ad63-46bd-b8e1-57696b382985\",\"history\":[{\"message\":\"你好\",\"type\":\"userMessage\"},{\"message\":\"你好！很高兴见到你。有什么我可以帮助你的吗？\",\"type\":\"apiMessage\"}]}";
        IncomingInput incomingInput = JSON.parseObject(incomingInputStr, IncomingInput.class);
        String parse = new FlowParse().parse(chatflowId, incomingInput);
        System.out.println(1);
    }

    /**
     *
     * @param chatflowId：restful url上的id
     * @param incomingInput：post body内容
     * @return
     *
     * 数据示例：
     * {
     * 	"question": "你好",
     *      "chatId": "19e7f2cd-ad63-46bd-b8e1-57696b382985",
     * 	"history": [
     *                {
     * 			"message": "你好",
     * 			"type": "userMessage"
     *        },
     *        {
     * 			"message": "你好！很高兴见到你。有什么我可以帮助你的吗？",
     * 			"type": "apiMessage"
     *        },
     *        {
     * 			"message": "武汉美食",
     * 			"type": "userMessage"
     *        },
     *        {
     * 			"message": "武汉，又称江城，是湖北省的省会，也是中国中部的一座大城市。它以其独特的地理位置和丰富的文化底蕴而闻名，也以其美味的食物吸引了无数的游客。\n以下是几种著名的武汉美食：\n\n1. 热干面：这是一种非常受欢迎的地方小吃，由热面条、芝麻酱、醋、葱花等组成。它的口感独特，既有面条的韧性，又有芝麻酱的香醇。\n\n2. 江汉路烤鱼：这是一道以长江鲜鱼为主料的传统菜肴。鱼肉鲜嫩，烤制后外酥里嫩，香气四溢。\n\n3. 三鲜豆皮：这是一种以豆腐皮为主要原料的小吃，里面包裹着虾仁、猪肉末和香菇等配料，口感鲜美。\n\n4. 武昌鱼：这也是武汉特色的一道菜，主要以武昌鱼为主料，配以豆瓣酱、花椒、姜片等调料，味道鲜美。\n\n5. 油炸臭豆腐：这是武汉的一种街头小吃，选用新鲜的豆腐经过油炸后，再加入特制的调味汁，口感香脆可口。\n\n除了以上这些，还有很多其他的武汉美食值得一试，例如鸭血粉丝汤、排骨藕汤、糯米鸡等。在武汉旅游时，一定要品尝一下当地的美食，体验一下这座城市的独特魅力。",
     * 			"type": "apiMessage"
     *        }
     * 	]
     * }
     */
    public String parse(String chatflowId, IncomingInput incomingInput){
        // todo.. 获取流程信息 from 数据库流程表
        Chatflow chatflow = new Chatflow();
        String flowStr = "{\"nodes\":[{\"width\":300,\"height\":578,\"id\":\"chatLocalAI_0\",\"position\":{\"x\":-45.43868954758196,\"y\":88.01622464898597},\"type\":\"customNode\",\"data\":{\"label\":\"ChatLocalAI\",\"name\":\"chatLocalAI\",\"version\":2,\"type\":\"ChatLocalAI\",\"icon\":\"D:/workspace/bm_idea_workspace/docker_project/Flowise/node_modules/flowise-components/dist/nodes/chatmodels/ChatLocalAI/localai.png\",\"category\":\"Chat Models\",\"description\":\"Use local LLMs like llama.cpp, gpt4all using LocalAI\",\"baseClasses\":[\"ChatLocalAI\",\"BaseChatModel\",\"LLM\",\"BaseLLM\",\"BaseLanguageModel\",\"Runnable\"],\"inputs\":{\"cache\":\"\",\"basePath\":\"http://pre-llm.zebred.com/qwen\",\"modelName\":\"Qwen\",\"temperature\":\"0.7\",\"maxTokens\":\"\",\"topP\":\"\",\"timeout\":\"\"},\"filePath\":\"D:\\\\workspace\\\\bm_idea_workspace\\\\docker_project\\\\Flowise\\\\node_modules\\\\flowise-components\\\\dist\\\\nodes\\\\chatmodels\\\\ChatLocalAI\\\\ChatLocalAI.js\",\"inputAnchors\":[{\"label\":\"Cache\",\"name\":\"cache\",\"type\":\"BaseCache\",\"optional\":true,\"id\":\"chatLocalAI_0-input-cache-BaseCache\"}],\"inputParams\":[{\"label\":\"Base Path\",\"name\":\"basePath\",\"type\":\"string\",\"placeholder\":\"http://localhost:8080/v1\",\"id\":\"chatLocalAI_0-input-basePath-string\"},{\"label\":\"Model Name\",\"name\":\"modelName\",\"type\":\"string\",\"placeholder\":\"gpt4all-lora-quantized.bin\",\"id\":\"chatLocalAI_0-input-modelName-string\"},{\"label\":\"Temperature\",\"name\":\"temperature\",\"type\":\"number\",\"step\":0.1,\"default\":0.9,\"optional\":true,\"id\":\"chatLocalAI_0-input-temperature-number\"},{\"label\":\"Max Tokens\",\"name\":\"maxTokens\",\"type\":\"number\",\"step\":1,\"optional\":true,\"additionalParams\":true,\"id\":\"chatLocalAI_0-input-maxTokens-number\"},{\"label\":\"Top Probability\",\"name\":\"topP\",\"type\":\"number\",\"step\":0.1,\"optional\":true,\"additionalParams\":true,\"id\":\"chatLocalAI_0-input-topP-number\"},{\"label\":\"Timeout\",\"name\":\"timeout\",\"type\":\"number\",\"step\":1,\"optional\":true,\"additionalParams\":true,\"id\":\"chatLocalAI_0-input-timeout-number\"}],\"outputs\":{},\"outputAnchors\":[{\"id\":\"chatLocalAI_0-output-chatLocalAI-ChatLocalAI|BaseChatModel|LLM|BaseLLM|BaseLanguageModel|Runnable\",\"name\":\"chatLocalAI\",\"label\":\"ChatLocalAI\",\"type\":\"ChatLocalAI | BaseChatModel | LLM | BaseLLM | BaseLanguageModel | Runnable\"}],\"id\":\"chatLocalAI_0\",\"selected\":false},\"selected\":false,\"positionAbsolute\":{\"x\":-45.43868954758196,\"y\":88.01622464898597},\"dragging\":false},{\"width\":300,\"height\":511,\"id\":\"promptTemplate_0\",\"position\":{\"x\":341.6224648985959,\"y\":109.70249609984398},\"type\":\"customNode\",\"data\":{\"label\":\"Prompt Template\",\"name\":\"promptTemplate\",\"version\":1,\"type\":\"PromptTemplate\",\"icon\":\"D:/workspace/bm_idea_workspace/docker_project/Flowise/node_modules/flowise-components/dist/nodes/prompts/PromptTemplate/prompt.svg\",\"category\":\"Prompts\",\"description\":\"Schema to represent a basic prompt for an LLM\",\"baseClasses\":[\"PromptTemplate\",\"BaseStringPromptTemplate\",\"BasePromptTemplate\",\"Runnable\"],\"inputs\":{\"template\":\"你是一个美食家\",\"promptValues\":\"\"},\"filePath\":\"D:\\\\workspace\\\\bm_idea_workspace\\\\docker_project\\\\Flowise\\\\node_modules\\\\flowise-components\\\\dist\\\\nodes\\\\prompts\\\\PromptTemplate\\\\PromptTemplate.js\",\"inputAnchors\":[],\"inputParams\":[{\"label\":\"Template\",\"name\":\"template\",\"type\":\"string\",\"rows\":4,\"placeholder\":\"What is a good name for a company that makes {product}?\",\"id\":\"promptTemplate_0-input-template-string\"},{\"label\":\"Format Prompt Values\",\"name\":\"promptValues\",\"type\":\"json\",\"optional\":true,\"acceptVariable\":true,\"list\":true,\"id\":\"promptTemplate_0-input-promptValues-json\"}],\"outputs\":{},\"outputAnchors\":[{\"id\":\"promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable\",\"name\":\"promptTemplate\",\"label\":\"PromptTemplate\",\"type\":\"PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate | Runnable\"}],\"id\":\"promptTemplate_0\",\"selected\":false},\"selected\":true,\"positionAbsolute\":{\"x\":341.6224648985959,\"y\":109.70249609984398},\"dragging\":false},{\"width\":300,\"height\":507,\"id\":\"llmChain_0\",\"position\":{\"x\":755.2396255850234,\"y\":119.62137285491411},\"type\":\"customNode\",\"data\":{\"label\":\"LLM Chain\",\"name\":\"llmChain\",\"version\":3,\"type\":\"LLMChain\",\"icon\":\"D:/workspace/bm_idea_workspace/docker_project/Flowise/node_modules/flowise-components/dist/nodes/chains/LLMChain/LLM_Chain.svg\",\"category\":\"Chains\",\"description\":\"Chain to run queries against LLMs\",\"baseClasses\":[\"LLMChain\",\"BaseChain\",\"Runnable\"],\"inputs\":{\"model\":\"{{chatLocalAI_0.data.instance}}\",\"prompt\":\"{{promptTemplate_0.data.instance}}\",\"outputParser\":\"\",\"inputModeration\":\"\",\"chainName\":\"\"},\"outputs\":{\"output\":\"llmChain\"},\"filePath\":\"D:\\\\workspace\\\\bm_idea_workspace\\\\docker_project\\\\Flowise\\\\node_modules\\\\flowise-components\\\\dist\\\\nodes\\\\chains\\\\LLMChain\\\\LLMChain.js\",\"inputAnchors\":[{\"label\":\"Language Model\",\"name\":\"model\",\"type\":\"BaseLanguageModel\",\"id\":\"llmChain_0-input-model-BaseLanguageModel\"},{\"label\":\"Prompt\",\"name\":\"prompt\",\"type\":\"BasePromptTemplate\",\"id\":\"llmChain_0-input-prompt-BasePromptTemplate\"},{\"label\":\"Output Parser\",\"name\":\"outputParser\",\"type\":\"BaseLLMOutputParser\",\"optional\":true,\"id\":\"llmChain_0-input-outputParser-BaseLLMOutputParser\"},{\"label\":\"Input Moderation\",\"description\":\"Detect text that could generate harmful output and prevent it from being sent to the language model\",\"name\":\"inputModeration\",\"type\":\"Moderation\",\"optional\":true,\"list\":true,\"id\":\"llmChain_0-input-inputModeration-Moderation\"}],\"inputParams\":[{\"label\":\"Chain Name\",\"name\":\"chainName\",\"type\":\"string\",\"placeholder\":\"Name Your Chain\",\"optional\":true,\"id\":\"llmChain_0-input-chainName-string\"}],\"outputAnchors\":[{\"name\":\"output\",\"label\":\"Output\",\"type\":\"options\",\"options\":[{\"id\":\"llmChain_0-output-llmChain-LLMChain|BaseChain|Runnable\",\"name\":\"llmChain\",\"label\":\"LLM Chain\",\"type\":\"LLMChain | BaseChain | Runnable\"},{\"id\":\"llmChain_0-output-outputPrediction-string|json\",\"name\":\"outputPrediction\",\"label\":\"Output Prediction\",\"type\":\"string | json\"}],\"default\":\"llmChain\"}],\"id\":\"llmChain_0\",\"selected\":false},\"selected\":false,\"positionAbsolute\":{\"x\":755.2396255850234,\"y\":119.62137285491411},\"dragging\":false}],\"edges\":[{\"source\":\"promptTemplate_0\",\"sourceHandle\":\"promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable\",\"target\":\"llmChain_0\",\"targetHandle\":\"llmChain_0-input-prompt-BasePromptTemplate\",\"type\":\"buttonedge\",\"id\":\"promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate\"},{\"source\":\"chatLocalAI_0\",\"sourceHandle\":\"chatLocalAI_0-output-chatLocalAI-ChatLocalAI|BaseChatModel|LLM|BaseLLM|BaseLanguageModel|Runnable\",\"target\":\"llmChain_0\",\"targetHandle\":\"llmChain_0-input-model-BaseLanguageModel\",\"type\":\"buttonedge\",\"id\":\"chatLocalAI_0-chatLocalAI_0-output-chatLocalAI-ChatLocalAI|BaseChatModel|LLM|BaseLLM|BaseLanguageModel|Runnable-llmChain_0-llmChain_0-input-model-BaseLanguageModel\"}],\"viewport\":{\"x\":258.97011638880144,\"y\":-59.59971689210437,\"zoom\":1.008178672538534}}";
        chatflow.setFlowData(flowStr);
        if(chatflow==null){
            // todo.. 抛出异常
            throw new RuntimeException("Chatflow ${chatflowid} not found");
        }

        String inputChatId = incomingInput.getChatId();
        JSONObject inputOverrideConfig = incomingInput.getOverrideConfig();
        String sessionId = inputOverrideConfig==null ? null : inputOverrideConfig.getString("sessionId");
        // 生成该次chat id
        String chatId = StringUtils.isNotEmpty(inputChatId) ?  inputChatId :
                StringUtils.isNotEmpty(sessionId) ? sessionId : UUID.randomUUID().toString();
        Date userMessageDateTime = new Date();

//        const files = (req.files as any[]) || []  // 上传文件
//
//        if (files.length) {
//                const overrideConfig: ICommonObject = { ...req.body }
//            for (const file of files) {
//                    const fileData = fs.readFileSync(file.path, { encoding: 'base64' })  // 读取文件数据，阻塞式，大文件慎用
//                    const dataBase64String = `data:${file.mimetype};base64,${fileData},filename:${file.filename}`
//
//                    const fileInputField = mapMimeTypeToInputField(file.mimetype) // 文件类型
//                if (overrideConfig[fileInputField]) {
//                    overrideConfig[fileInputField] = JSON.stringify([...JSON.parse(overrideConfig[fileInputField]), dataBase64String])
//                } else {
//                    overrideConfig[fileInputField] = JSON.stringify([dataBase64String])
//                }
//            }
//            incomingInput = {
//                    question: req.body.question ?? 'hello',
//                    overrideConfig,
//                    history: [],
//            socketIOClientId: req.body.socketIOClientId
//                }
//        }

        String flowData = chatflow.getFlowData();
        JSONObject parsedFlowData = JSON.parseObject(flowData);
        // nodes
        List<ReactFlowNode> nodes = JSON.parseArray(parsedFlowData.getString("nodes"), ReactFlowNode.class);
        // edges
        List<ReactFlowEdge> edges = JSON.parseArray(parsedFlowData.getString("edges"), ReactFlowEdge.class);

        // todo.. hy 111111 isFlowReusable 判断
        boolean isFlowReusable = false;

        if (isFlowReusable) { // 判断是否需要重新build langchain节点
            // end节点数据，包含inputAnchors(入锚点信息)、inputParams(入参)、outputAnchors(出锚点信息)
//            nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData as INodeData
            //todo.. hy 111111 添加到缓存


        } else {

            Map<String, Boolean> options = new HashMap<>();
            options.put("isNonDirected", false);
            options.put("isReversed", false);
            /********** 正向graphs **********/
            Map<String, Object> graphs = constructGraphs(nodes, edges, options);
            Map<String, List<String>> graph = (Map<String, List<String>>) graphs.get("graph");
            Map<String, Integer> nodeDependencies = (Map<String, Integer>) graphs.get("nodeDependencies");

            /********** 获取终点节点 **********/
            List<String> endingNodeIds = getEndingNodes(nodeDependencies, graph);
            if (endingNodeIds.isEmpty()) {
                // todo.. hy 111111
//                return res.status(500).send("Ending nodes not found");
            }
            // 终点节点数据
            List<ReactFlowNode> endingNodes = nodes.stream().filter(nd -> endingNodeIds.contains(nd.getId())).collect(Collectors.toList());
            for (ReactFlowNode endingNode : endingNodes) {
                NodeData endingNodeData = endingNode.getData();
                if (endingNodeData == null) {
                    // todo.. hy 111111
//                    return res.status(500).send("Ending node " + endingNode.getId() + " data not found");
                }
                if (!endingNodeData.getCategory().equals("Chains") && !endingNodeData.getCategory().equals("Agents")) {
                    // todo.. hy 111111
//                    return res.status(500).send("Ending node must be either a Chain or Agent");
                }
            }

            /********** 反向graphs **********/
            Map<String, Object> constructedObj = constructGraphs(nodes, edges, Collections.singletonMap("isReversed", true));
            Map<String, List<String>> nonDirectedGraph = (Map<String, List<String>>) constructedObj.get("graph");
            List<String> startingNodeIds = Lists.newArrayList();
            Map<String, Integer> depthQueue = Maps.newHashMap();
            for (String endingNodeId : endingNodeIds) {
                /********** 获取开始节点 **********/
                Map<String, Object> res = getStartingNodes(nonDirectedGraph, endingNodeId);
                startingNodeIds.addAll((List<String>) res.get("startingNodeIds"));

                /**
                 * flowis流程有bug
                 *
                 *                        ○                 ○                        深度      --- 0
                 *                          \             /
                 *                           ○        ○   <== 注释1                       --- 1
                 *                           /      /   \
                 *                       ○      /       ○                                         --- 2
                 *                        \   /           \
                 *     注释2 ==>   ○              ○                                       --- 2 or 3
                 *                          \            /
                 *                           \        /
                 *                               ○                                                 --- 4
                 *
                 *  【注释1】：if else function 可能会出现分支，例如if else走不同变量设置
                 *  【注释2】：同一个节点深度在不同路线可是2或者3，应该取最大值，保证执行顺序在依赖节点之后
                 */
                Map<String, Integer> nextDepthQueue = (Map<String, Integer>) res.get("depthQueue");
                nextDepthQueue.forEach((key, value) ->
                        depthQueue.merge(key, value, (v1, v2) -> Math.max(v1, v2))
                );

            }
            // 去重
            List<String> uniqueStartingNodeIds = Lists.newArrayList(Sets.newHashSet(startingNodeIds));
            List<ReactFlowNode> startingNodes = nodes.stream().filter(nd -> uniqueStartingNodeIds.contains(nd.getId()))
                    .collect(Collectors.toList());

            /** 生成Python代码 */
            generatePythonCode(startingNodes);


        }


        return null;

    }

    public Map<String, Object> constructGraphs(List<ReactFlowNode> reactFlowNodes, List<ReactFlowEdge> reactFlowEdges, Map<String, Boolean> options) {
        Map<String, Integer> nodeDependencies = Maps.newHashMap();
        Map<String, List<String>> graph = Maps.newHashMap();

        // reactFlowNodes
        for (int i = 0; i < reactFlowNodes.size(); i++) {
            String nodeId = reactFlowNodes.get(i).getId();
            nodeDependencies.put(nodeId, 0);
            graph.put(nodeId, new ArrayList<>());
        }

        // isReversed
        if (options != null && BooleanUtils.isTrue(options.get("isReversed"))) {
            // reactFlowEdges  target -> source
            for (int i = 0; i < reactFlowEdges.size(); i++) {
                String source = reactFlowEdges.get(i).getSource();
                String target = reactFlowEdges.get(i).getTarget();
                if (graph.containsKey(target)) {
                    graph.get(target).add(source);
                } else {
                    graph.put(target, Lists.newArrayList(source));
                }
                nodeDependencies.put(target, nodeDependencies.get(target) + 1);
            }
            Map<String, Object> result = Maps.newHashMap();
            result.put("graph", graph);
            result.put("nodeDependencies", nodeDependencies);
            return result;
        }

        // reactFlowEdges  source -> target
        for (int i = 0; i < reactFlowEdges.size(); i++) {
            String source = reactFlowEdges.get(i).getSource();
            String target = reactFlowEdges.get(i).getTarget();
            if (graph.containsKey(source)) {
                graph.get(source).add(target);
            } else {
                graph.put(source, new ArrayList<>(Collections.singletonList(target)));
            }

            // isNonDirected
            if (options != null && BooleanUtils.isTrue(options.get("isNonDirected"))) {
                if (graph.containsKey(target)) {
                    graph.get(target).add(source);
                } else {
                    graph.put(target, Lists.newArrayList(source));
                }
            }
            nodeDependencies.put(target, nodeDependencies.get(target) + 1);
        }
        Map<String, Object> result = Maps.newHashMap();
        result.put("graph", graph);
        result.put("nodeDependencies", nodeDependencies);
        return result;
    }

    public List<String> getEndingNodes(Map<String, Integer> nodeDependencies, Map<String, List<String>> graph) {
        List<String> endingNodeIds = new ArrayList<>();
        for (String nodeId : graph.keySet()) {
            if (nodeDependencies.size() == 1) {
                endingNodeIds.add(nodeId);
            } else if (CollectionUtils.isEmpty(graph.get(nodeId)) && nodeDependencies.get(nodeId) > 0) {
                endingNodeIds.add(nodeId);
            }
        }
        return endingNodeIds;
    }

    public Map<String, Object> getStartingNodes(Map<String, List<String>> graph, String endNodeId) {
        Set<String> visited = new HashSet<>();
        List<Pair<String, Integer>> queue = new LinkedList<>();
        queue.add(new MutablePair<>(endNodeId, 0));

        Map<String, Integer> depthQueue = new HashMap<>();
        int maxDepth = 0;
        List<String> startingNodeIds = new ArrayList<>();

        depthQueue.put(endNodeId, 0);

        while (!queue.isEmpty()) {
            Pair<String, Integer> currentNode = queue.remove(0);
            String nodeId = currentNode.getKey();
            int depth = currentNode.getValue();

            if (visited.contains(nodeId)) {
                continue;
            }

            visited.add(nodeId);

            if (depth > maxDepth) {
                maxDepth = depth;
                startingNodeIds = Lists.newArrayList(nodeId);
            } else if (depth == maxDepth) {
                startingNodeIds.add(nodeId);
            }

            List<String> neighbors = graph.get(nodeId);
            if (neighbors != null) {
                for (String neighbor : neighbors) {
                    if (!visited.contains(neighbor)) {
                        queue.add(new MutablePair<>(neighbor, depth + 1));
                        depthQueue.put(neighbor, depth + 1);
                    }
                }
            }
        }

        Map<String, Integer> depthQueueReversed = Maps.newHashMap();
        for (Map.Entry<String, Integer> entry : depthQueue.entrySet()) {
            String nodeId = entry.getKey();
            int depth = entry.getValue();
            depthQueueReversed.put(nodeId, Math.abs(depth - maxDepth));
        }

        Map<String, Object> result = Maps.newHashMap();
        result.put("startingNodeIds", startingNodeIds);
        result.put("depthQueue", depthQueueReversed);
        return result;
    }

    private void generatePythonCode(List<ReactFlowNode> startingNodes){
        // todo.. hy 111111
        for(ReactFlowNode startingNode : startingNodes) {
            NodeData startingNodeData = startingNode.getData();
            // Python类名
            String name = startingNodeData.getName();
            // Python对象invoke入参
            JSONObject inputs = startingNodeData.getInputs();

        }
    }


}
